﻿using System;
using System.Collections.Generic;
using System.Linq;
using FizzBuzzConsoleClient;
using StoryQ;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace AcceptanceTests
{
    [TestClass]
    public class StoryQTestClass
    {
        private string _consoleOutput;
        private Action _fizzBuzzProgram;

        [TestMethod]
        public void FizzBuzz()
        {
            new Story("Fizz Buzz")
                .InOrderTo("solve the Fizz Buzz problem")
                .AsA("User")
                .IWant("to be able to print the Fizz Buzz numbers from 1 to 100")

                .WithScenario("Print numbers 1 to 100")
                .Given(TheFizzBuzzProgram)
                .When(TheFizzBuzzProgramIsRun)
                .Then(ItPrintsTheNumbersFrom1To100WhereNotAMultipleOf3Or5)

                .WithScenario("For multiples of 3 print Fizz instead of the number")
                .Given(TheFizzBuzzProgram)
                .When(TheFizzBuzzProgramIsRun)
                .Then(ItPrintsFizzForAllNumbersThatAreAMultipleOf3Only)

                .WithScenario("For multiples of 5 print Buzz instead of the number")
                .Given(TheFizzBuzzProgram)
                .When(TheFizzBuzzProgramIsRun)
                .Then(ItPrintsBuzzForAllNumbersThatAreAMultipleOf5Only)

                .WithScenario("For multiples of 3 and 5 print FizzBuzz instead of the number")
                .Given(TheFizzBuzzProgram)
                .When(TheFizzBuzzProgramIsRun)
                .Then(ItPrintsFizzBuzzForAllNumbersThatAreMultiplesOf3And5)
                .Execute();
        }

        private void TheFizzBuzzProgram()
        {
            _fizzBuzzProgram = () => Program.Main();
        }

        private void TheFizzBuzzProgramIsRun()
        {
            using (var consoleRedirector = new ConsoleRedirector(false))
            {
                _fizzBuzzProgram.Invoke();
                _consoleOutput = consoleRedirector.ToString();
            }
        }

        private void ItPrintsTheNumbersFrom1To100WhereNotAMultipleOf3Or5()
        {
            Func<int, bool> checkNumberPredicate = number => number % 3 != 0 && number % 5 != 0;
            Func<int, string> getExpectedResult = number => number.ToString();
            AssertResults(checkNumberPredicate, getExpectedResult);
        }

        private void ItPrintsFizzForAllNumbersThatAreAMultipleOf3Only()
        {
            Func<int, bool> checkNumberPredicate = number => number % 3 == 0 && number % 5 != 0;
            Func<int, string> getExpectedResult = number => "Fizz";
            AssertResults(checkNumberPredicate, getExpectedResult);
        }

        private void ItPrintsBuzzForAllNumbersThatAreAMultipleOf5Only()
        {
            Func<int, bool> checkNumberPredicate = number => number % 3 != 0 && number % 5 == 0;
            Func<int, string> getExpectedResult = number => "Buzz";
            AssertResults(checkNumberPredicate, getExpectedResult);
        }

        private void ItPrintsFizzBuzzForAllNumbersThatAreMultiplesOf3And5()
        {
            Func<int, bool> checkNumberPredicate = number => number % 3 == 0 && number % 5 == 0;
            Func<int, string> getExpectedResult = number => "FizzBuzz";
            AssertResults(checkNumberPredicate, getExpectedResult);
        }

        private void AssertResults(Func<int, bool> checkNumberPredicate, Func<int, string> getExpectedResult)
        {
            var lines = _consoleOutput.Split(new[] {'\r', '\n'}, StringSplitOptions.RemoveEmptyEntries);
            Assert.AreEqual(100, lines.Length);
            var failures = new List<string>();
            for (var index = 0; index < lines.Length; index++)
            {
                var number = index + 1;
                if (!checkNumberPredicate(number)) continue;
                var actualResult = lines[index];
                var expectedResult = getExpectedResult(number);
                if (!expectedResult.Equals(actualResult))
                {
                    failures.Add(string.Format("Given Number <{0}> Expected <{1}> Actual <{2}>",
                                               number,
                                               expectedResult,
                                               actualResult));
                }
            }
            if (failures.Any())
            {
                Assert.Fail(string.Format("{0}Failures:{0}{1}",
                                          Environment.NewLine,
                                          string.Join(Environment.NewLine, failures)));
            }
        }
    }
}
